package org.xteam.sled.model;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.xteam.sled.semantic.Field;
import org.xteam.sled.semantic.TokenClass;
import org.xteam.sled.semantic.exp.Exp;
import org.xteam.sled.semantic.exp.IExpRewriter;

public class Constraints implements ILabelledSequent, ILatentSequent {
	protected TokenClass tokenClass;
	protected Map<Field, Exp> fieldBindings = new HashMap<Field, Exp>();
	protected List<RangeConstraint<Field>> rangeConstraints = new ArrayList<RangeConstraint<Field>>();
	
	public Constraints() {
		this(null);
	}
	
	public Constraints(TokenClass cls) {
		this.tokenClass = cls;
	}
	
	public Constraints(TokenClass tokenClass, Map<Field, Exp> fieldBindings,
			List<RangeConstraint<Field>> rangeConstraints) {
		this.tokenClass = tokenClass;
		this.fieldBindings = fieldBindings;
		this.rangeConstraints = rangeConstraints;
	}

	@Override
	public boolean hasInputOrLabels() {
		return fieldBindings.size() > 0;
	}

	public void add(RangeConstraint<Field> ctr) {
		if (tokenClass == null)
			tokenClass = ctr.field.cls();
		this.rangeConstraints.add(ctr);
	}

	public void add(Map<Field, Exp> exprs) {
		this.fieldBindings.putAll(exprs);
	}
	
	@Override
	public int width() {
		return tokenClass.size();
	}
	

	public List<RangeConstraint<Field>> getConstraints() {
		return rangeConstraints;
	}
	
	@Override
	public boolean isContradictory() {
		for (RangeConstraint<Field> cnst : rangeConstraints) {
			if (cnst.isContradictory()) {
				return true;
			}
		}
		return false;
	}
	
	@Override
	public ILabelledSequent actualize(boolean actualsOk) {
		return this;
	}

	@Override
	public boolean isLabel() {
		return false;
	}

	@Override
	public ILabelledSequent injectLatent() {
		return this;
	}

	@Override
	public Constraints merge(ISequent o) {
		Constraints other = (Constraints) o;
		if (tokenClass != other.tokenClass) {
			throw new RuntimeException(
					"Shapes differ for &; left sequent from class `"
							+ tokenClass.name()
							+ "'; right sequent from class `"
							+ other.tokenClass.name() + "'");
		}
		return new Constraints(tokenClass, mergeBindings(other.fieldBindings),
				mergeConstraints(other.rangeConstraints));
	}
	
	private Map<Field, Exp> mergeBindings(Map<Field, Exp> others) {
		Map<Field, Exp> result = new HashMap<Field, Exp>(fieldBindings);
		for (Field f : others.keySet()) {
			if (result.containsKey(f))
				throw new RuntimeException("Multiple field bindings for field `" + f.name() + "'");
			result.put(f, others.get(f));
		}
		return result;
	}
	
	private List<RangeConstraint<Field>> mergeConstraints(List<RangeConstraint<Field>> others) {
		Map<Field, RangeConstraint<Field>> result = new HashMap<Field, RangeConstraint<Field>>();
		for (RangeConstraint<Field> c : rangeConstraints) {
			if (result.containsKey(c.field())) {
				result.put(c.field(), c.intersection(result.get(c.field())));
			} else {
				result.put(c.field(), c);
			}
		}
		for (RangeConstraint<Field> c : others) {
			if (result.containsKey(c.field())) {
				result.put(c.field(), c.intersection(result.get(c.field())));
			} else {
				result.put(c.field(), c);
			}
		}
		return new ArrayList<RangeConstraint<Field>>(result.values());
	}
	
	@Override
	public Constraints subs(IExpRewriter subs) {
		Map<Field, Exp> newExprs = new HashMap<Field, Exp>();
		for (Field f : fieldBindings.keySet()) {
			newExprs.put(f, subs.rewrite(fieldBindings.get(f)));
		}
		return new Constraints(tokenClass, newExprs, rangeConstraints);
	}
	
	@Override
	public String toString() {
		StringBuffer buffer = new StringBuffer();
		boolean isFirst = true;
		if (fieldBindings.size() > 0) {
			buffer.append("{");
			for (Field f : fieldBindings.keySet()) {
				if (!isFirst)
					buffer.append(", ");
				buffer.append(f.name()).append("=")
						.append(fieldBindings.get(f));
				isFirst = false;
			}
			buffer.append("}");
		}
		isFirst = true;
		for (RangeConstraint<Field> rc : rangeConstraints) {
			if (! isFirst)
				buffer.append(" ");
			buffer.append(rc.toString());
			isFirst = false;
		}
		return buffer.toString();
	}

	public static Constraints newConstraint(RangeConstraint<Field> ctr) {
		Constraints ctrs = new Constraints();
		ctrs.add(ctr);
		return ctrs;
	 }
	
	public static Constraints newConstraint(TokenClass cls, Map<Field, Exp> exprs) {
		Constraints ctrs = new Constraints(cls);
		ctrs.add(exprs);
		return ctrs;
	}

}
